package com.beiding.monitorfile;

import org.springframework.util.StreamUtils;
/*
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
*/

import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

//受监管的文件
public class MonitorFile {
/*

    //目标文件夹的大小
    private long size;

    //临界
    private long maxSize = -1;

    private File file;

    private Map<String, File> fMap = new HashMap<>();

    //扫描给定路径下的所有文件
    private void scan(File file) {

        File[] fs = file.listFiles();
        if (fs != null) {
            for (File f : fs) {
                size += f.length();
                fMap.put(f.getAbsolutePath().replace(this.file.getAbsolutePath(), "").replace("\\", "/"), f);
                //持续遍历扫描
                if (f.isDirectory()) {
                    scan(f);
                }
            }
        }
    }

    private void deepClear(File file) {
        File[] fs = file.listFiles();
        if (fs != null) {
            for (File f : fs) {
                if (f.isDirectory()) {
                    deepClear(f);
                }
                f.delete();

                size -= f.length();
            }
        }
    }

    //单例
    private static Map<String, MonitorFile> fileMap = new HashMap<>();

    public static MonitorFile create(File file) {

        //如果文件夹不存在就创建一个文件夹

        //必须是文件夹,如果不是文件就报错

        synchronized (MonitorFile.class) {

            MonitorFile monitorFile = fileMap.get(file.getAbsolutePath());

            if (monitorFile != null) {
                return monitorFile;
            }

            if (!file.exists()) {
                if (!file.mkdir()) {
                    throw new RuntimeException("文件创建失败");
                }
            }

            if (!file.isDirectory()) {
                throw new RuntimeException("只能创建文件夹对象");
            }

            //读取文件夹大小
            monitorFile = new MonitorFile();

            //文件大小
            monitorFile.size = file.length();

            //给定
            monitorFile.file = file;

            fileMap.put(file.getAbsolutePath(), monitorFile);

            //扫描所有文件构建索引
            monitorFile.scan(file);

            return monitorFile;
        }

    }

    //只能设置一次大小
    public synchronized void setMaxSize(long maxSize) {
        if (this.maxSize != -1) {
            throw new RuntimeException("只能设置一次最大容量,你可以销毁该对象后重建");
        }
        if (maxSize < size) {
            throw new RuntimeException("当前文件夹大小(" + size + ")已经超出给定的最大容量,请重新指定");
        }
        this.maxSize = maxSize;
    }

    private MonitorFile() {

    }

    //使用完成之后进行销毁,如果不销毁就会一直存在
    public void destroy() {
        //移除当前文件
        synchronized (MonitorFile.class) {
            fileMap.remove(file.getAbsolutePath());
        }
    }


    public synchronized int write(String path, File file) {

        if (maxSize == -1) {
            throw new RuntimeException("未指定容量");
        }

        try {
            FileInputStream inputStream = new FileInputStream(file);
            int t = write(path, inputStream);
            inputStream.close();
            return t;
        } catch (Exception e) {
            return 3;
        }

    }

    */
/**
     * @param path 相对父节点的路径
     * @return 0 :正常写入,1 :文件名称已存在无法写入, 2 :文件夹已满无法继续写入 3:其他未知异常
     * @throws IOException
     *//*

    public synchronized int write(String path, InputStream inputStream) {

        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        File target = new File(file.getAbsolutePath() + path);
        try {
            //如果可以写入
            if (canWrite(inputStream)) {
                int available = inputStream.available();

                if (target.exists()) {
                    return 1;
                }
                File parentFile = target.getParentFile();
                if (!parentFile.exists()) {
                    parentFile.mkdirs();
                }
                target.createNewFile();
                FileOutputStream outputStream = new FileOutputStream(target);
                StreamUtils.copy(inputStream, outputStream);
                outputStream.close();
                size += available;
                fMap.put(target.getAbsolutePath().replace(file.getAbsolutePath(), "").replace("\\", "/"), target);
                return 0;
            } else {
                return 2;
            }

        } catch (Exception e) {
            if (target.exists()) {
                //如果写入的过程中出现任何异常将不完整的文件删除
                size -= target.length();
                target.delete();
            }
            return 3;
        }


    }


    */
/**
     * @param path
     * @return 0: 正常删除,1: 文件不存在, 2: 其他异常
     *//*

    public synchronized int delete(String path) {
        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        File file = fMap.get(path);
        if (file == null) {
            return 1;
        }
        try {
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                if (files != null) {
                    for (File f : files) {
                        //首先进行递归删除所有的后代节点,然后在删除该路径
                        delete(f.getAbsolutePath().replace(this.file.getAbsolutePath(), "").replace("\\", "/"));
                    }
                }
            }
            fMap.remove(path);
            long length = file.length();
            if (file.delete()) {
                size -= length;//需要递归删除
                return 0;
            }

            return 2;

        } catch (Exception e) {
            return 2;
        }
    }

    //判断文件是否可以写入
    public synchronized boolean canWrite(InputStream inputStream) throws IOException {

        //如果当前大小比最大尺寸小就可以写入
        return this.size + inputStream.available() <= maxSize;
    }

    public synchronized boolean canWrite(File file) {
        return this.size + file.length() <= maxSize;
    }


    public interface OnFullCallBack {
        void onFull(FileOperation fileOperation);
    }

    //创建一个文件操作器操作文件
    public FileOperation createFileOperation() {
        return this.new FileOperation();
    }


    public static abstract class ProgressHandler {
        public abstract void progress(double progress);

        //粒度,一个介于0到1之间的数字,
        private double particle = 0.1;

        public void setParticle(double particle) {
            if (particle <= 0 || particle > 1) {
                throw new RuntimeException("不支持的粒度");
            }
            this.particle = particle;
        }


    }

    public class FileOperation {

        //将所有的文件转移到一个给定路径中
        public synchronized void transfer(File root, ProgressHandler handler) throws IOException {


            Map<File, File> map = new HashMap<>();

            //为了确保转移的安全性,这里需要对所有目标文件进行检查
            for (File f : fMap.values()) {

                //移动到的新路径
                File nf = new File(f.getAbsolutePath().replace(file.getAbsolutePath(), root.getAbsolutePath()));

                map.put(f, nf);
                if (!nf.exists()) {
                    if (f.isDirectory()) {
                        nf.mkdirs();
                    } else {
                        File parentFile = nf.getParentFile();
                        if (!parentFile.exists()) {
                            parentFile.mkdirs();
                        }
                        nf.createNewFile();
                    }
                }
            }
            double allSize = 0;

            double cSize = 0;

            for (File f : map.keySet()) {
                File t = map.get(f);

                if (f.isDirectory()) {

                    allSize += f.length();
                    cSize += f.length();
                    if (cSize / size > handler.particle) {
                        handler.progress(allSize / size);
                        cSize = 0;
                    }
                    continue;
                }

                FileInputStream inputStream = new FileInputStream(f);
                FileOutputStream fileOutputStream = new FileOutputStream(t);

                //使用1024作为copy缓存
                byte[] buffer = new byte[1024];

                int len;
                while ((len = inputStream.read(buffer)) != -1) {
                    cSize += len;
                    allSize += len;
                    if (cSize / size > handler.particle) {
                        handler.progress(allSize / size);
                        cSize = 0;
                    }
                    fileOutputStream.write(buffer, 0, len);
                }
                inputStream.close();
                fileOutputStream.close();
            }

            //最终总是1
            handler.progress(1);

            //清空所有文件
            clear();

        }


        //清除所有文件
        public synchronized void clear() {
            deepClear(file);
            fMap.clear();
        }

    }

    public File getFile(String path) {
        if (!path.startsWith("/")) {
            path = "/" + path;
        }
        return fMap.get(path);
    }

    public long size() {
        return size;
    }

    public long getMaxSize() {
        return maxSize;
    }


    public void setMaxSize(String size) {

        String o = changeUnit(size);
        long max = Long.valueOf(o.substring(0, o.length() - 1));
        this.setMaxSize(max);
    }

    public String changeUnit(String o) {
        if (o.endsWith("G")) {
            long size = Long.valueOf(o.substring(0, o.length() - 1));
            return changeUnit(1024 * size + "M");
        } else if (o.endsWith("M")) {
            long size = Long.valueOf(o.substring(0, o.length() - 1));
            return changeUnit(1024 * size + "K");
        } else if (o.endsWith("K")) {
            long size = Long.valueOf(o.substring(0, o.length() - 1));
            return 1024 * size + "BBe";
        }
        throw new RuntimeException("不支持的单位");
    }


    public static void main(String[] args) throws IOException {

        //写入的demo
        writeDemo();

        //转译demo
        transferDemo();

    }


    private static void writeDemo() {

        //创建一个受监管的文件夹,如果受监管的文件必须是文件夹否则会报错
        MonitorFile monitorFile = MonitorFile.create(new File("C:\\Users\\dinglei\\Desktop\\受监管的文件"));

        //设置文件夹最大容量,支持单位G,M,K,BBe
        monitorFile.setMaxSize("2M");

        //将要被写入的文件
        File willRite = new File("C:\\Users\\dinglei\\Desktop\\1.docx");

        //测试写入,第一次写入将会成功
        System.out.println(monitorFile.write("dir/测试.docx", willRite) == 0);

        //测试写入,第二次再写入的时候由于文件夹中存在相同的路径,就会报错
        System.out.println(monitorFile.write("dir/测试.docx", willRite) == 1);

        //写入多个不重复的文件名都会成功

        int n = 0;

        while (true) {

            String f = "dir/测试" + (n++) + ".docx";
            int result = monitorFile.write(f, willRite);

            if (result == 0) {
                System.out.println("写入成功:" + f);
            } else if (result == 2) {
                System.out.println("文件夹已满");
                break;
            }

        }

        //删除一个文件,删除成功返回0,文件不存在返回1
        System.out.println(monitorFile.delete("dir/测试.docx") == 0);

        //再次尝试写入,将会成功
        System.out.println(monitorFile.write("dir/测试" + n + ".docx", willRite) == 0);

        //你可以通过路径读取这个文件
        System.out.println(monitorFile.getFile("dir/测试0.docx").getAbsolutePath());

    }


    private static void transferDemo() throws IOException {

        //创建一个受监管的文件夹,如果受监管的文件必须是文件夹否则会报错
        MonitorFile monitorFile = MonitorFile.create(new File("C:\\Users\\dinglei\\Desktop\\受监管的文件"));

        //将要被写入的文件
        File willRite = new File("C:\\Users\\dinglei\\Desktop\\1.docx");

        if (!monitorFile.canWrite(willRite)) {
            System.out.println("文件夹已满不可再写入");
        }

        FileOperation fileOperation = monitorFile.createFileOperation();

        ProgressHandler handler = new ProgressHandler() {
            @Override
            public void progress(double progress) {
                System.out.println("完成百分比:" + progress * 100 + "%");
            }
        };

        handler.setParticle(0.2);

        fileOperation.transfer(new File("C:\\Users\\dinglei\\Desktop\\受监管的文件移动"), handler);

    }


    Map<String, UploadContainer> uploadContainerHashMap = new HashMap<>();


    private static class UploadContainer {

        //转移百分比
        private double transferRate = 0;

        //受监管的文件夹
        private MonitorFile monitorFile;

    }

    @PostMapping("/upload")
    Integer upload(@RequestPart("file") MultipartFile file, String key) throws IOException {

        //这里key是一个业务主键,例如某个用户多次上传文件

        UploadContainer uploadContainer = uploadContainerHashMap.get(key);

        if (uploadContainer == null) {
            uploadContainer = new UploadContainer();

                                                            //这里使用临时文件创建受监管文件夹,可以根据实际需求创建
            uploadContainer.monitorFile = MonitorFile.create(File.createTempFile("temp-", ".tt"));
            uploadContainerHashMap.put(key, uploadContainer);
        }

        //这里使用随机数创建文件名称
        Random random = new Random();
        String name = "file-" + random.nextInt(100000);

        //把multipartFile中上传的文件,写入到受监管文件夹
        int result = uploadContainer.monitorFile.write(name, file.getInputStream());

        //当返回结果为2表明受监管文件夹已满,需要进行转移
        if (result == 2) {


            //开始处理转移逻辑
            FileOperation fileOperation = uploadContainer.monitorFile.createFileOperation();

            UploadContainer finalUploadContainer = uploadContainer;

                                                     //这里创建一个临时文件作为转译到的目录,可以根据业务需求自行创建
            fileOperation.transfer(File.createTempFile("temp-", ".tt"), new ProgressHandler() {
                @Override
                public void progress(double progress) {

                    //更新转移的百分比,供下面接口使用
                    finalUploadContainer.transferRate = progress;
                }
            });

        }

        //返回的结果如果是0表示成功,如果是2前台就应该轮询下面的接口查看进度
        return result;

    }


    //当上面这个接口返回给前台为2的时候,就应该轮询这个接口(例如,0.5s轮询一次),查看转移进度,直到返回为1(转移完成)再重试上面的接口进行上传
    @GetMapping("/getProgress")
    Double getRate(String key) {

        UploadContainer uploadContainer = uploadContainerHashMap.get(key);

        //如果key不存在,表明没有这个业务主键
        if (uploadContainer == null) {
            return null;
        } else {

            //将转移百分比返回,当返回为1的时候表明转移完成,这时调用上面的接口重新上传由于受监管目录已满而上传失败的文件
            return uploadContainer.transferRate;
        }

    }
*/


}
